#[macro_use] extern crate log;

use std::os::raw::{c_char, c_int};

pub use ibm_mq_sys as mqi;

pub mod mqmessage;
pub mod mqueuemanager;
pub mod mqueue;
pub mod mqheaders;

pub use mqmessage::{MQMessage, MQMD} ;
pub use mqueuemanager::{MQueueManager, MQOpenOpts};
pub use mqueue::{MQueue, MQGMO, MQPMO, MQTopic, MQSD};
pub use mqheaders::{MQHeader};

#[allow(dead_code)]
extern "C" {
    fn mqic_connect(qm: *const c_char,
                    channel: *const c_char,
                    host: *const c_char, user: *const c_char, password: *const c_char,
                    cc: *mut c_int, rc: *mut c_int) -> c_int;
    fn mqic_open(conn_handler: c_int, qm_name: *const c_char, obj_name: *const c_char,
                 cc: *mut c_int, rc: *mut c_int) -> c_int;
    fn mqic_disconnect(conn_handler: c_int, cc: *mut c_int, rc: *mut c_int);
    fn mqic_close(conn_handler: c_int, obj_handler: *const c_int, cc: *mut c_int, rc: *mut c_int);
    fn mqic_put(conn_handler: c_int, obj_handler: c_int,
                msg: *const c_char, msg_descriptor: mqi::MQMD,
                cc: *mut c_int, rc: *mut c_int);
    fn mqic_get(conn_handler: c_int, obj_handler: c_int,
                buff: *mut i8, bufflen: c_int,
                cc: *mut c_int, rc: *mut c_int) -> c_int;
    fn set_mqcd_default(mqcd: *mut mqi::MQCD);   
    fn set_mqcno_default(mqcno: *mut mqi::MQCNO);
    fn set_mqod_default(mqod: *mut mqi::MQOD);
    fn set_mqgmo_default(mqgmo: *mut mqi::MQGMO);
    fn set_mqpmo_default(mqpmo: *mut mqi::MQPMO);
    fn set_mqmd_default(mqmd: *mut mqi::MQMD);
    fn set_mqsd_default(mqmd: *mut mqi::MQSD);
    fn set_mqcih_default(mqmd: *mut mqi::MQCIH);
    fn set_mqrfh2_default(mqmd: *mut mqi::MQRFH2);
}

#[derive(Debug)]
pub struct Error(Box<dyn std::error::Error>);

pub type Result<T> = std::result::Result<T, Error>;

#[derive(Debug)]
pub enum MQError {
    ConnectFailed { cc: ComplCode, rc: ReasonCode },
    OpenFailed { obj: String, cc: ComplCode, rc: ReasonCode },
    PutFailed{ obj: String, msg: String, cc: ComplCode, rc: ReasonCode },
    GetFailed{ obj: String, cc: ComplCode, rc: ReasonCode },
    SubscribeFailed { obj: String, cc: ComplCode, rc: ReasonCode },
    WrongObjectType { expected: String, actual: String  },
    QMNameNotSpecified,
}

impl MQError {
    fn description(&self) -> String {
        use MQError::*;
        match self {
            ConnectFailed{cc, rc} => format!("Failed to connect, CC: {}, RC: {}", cc, rc),
            OpenFailed { obj, cc, rc }  => format!("Failed to open {}, CC: {}, RC: {}", obj, cc, rc),
            PutFailed{ obj, msg, cc, rc } =>
                format!("Failed to PUT msg to {}, CC: {}, RC: {}\n{:?}", obj, cc, rc, msg),
            GetFailed{ obj, cc, rc } => format!("Failed to GET msg from {}, CC: {}, RC: {}", obj, cc, rc),
            SubscribeFailed { obj, cc, rc } =>
                format!("Failed to subscribe to {}, CC: {}, RC: {}", obj, cc, rc),
            WrongObjectType{expected, actual } =>
                format!("Wrong MQ object type, expected {}, actual {}", expected, actual),
            QMNameNotSpecified => "QM name o reference not specified".to_owned(),
           
        }
    }
}

impl From<MQError> for Error {
    fn from(e:  MQError) -> Self {
        Error(Box::new(e))
    }
}

impl std::fmt::Display for MQError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f,"{}", self.description())
    }
}

impl std::error::Error for MQError {
    // fn description(&self) -> &str {
        // &self.description()
    // }
}

impl From<std::ffi::NulError> for Error {
    fn from(e: std::ffi::NulError) -> Self {
        Error(Box::new(e))
    }
}

impl From<std::str::Utf8Error> for Error {
    fn from(e: std::str::Utf8Error) -> Self {
        Error(Box::new(e))
    }
}


fn strcpy(inp: &[u8], out: &mut [i8], len: usize){
    if len > 0 {
        for i in 0..len {
            out[i] = inp[i] as i8
        }
    }
}

#[derive(Debug,PartialEq)]
pub struct ComplCode(i32);
impl ComplCode {
    pub fn is_ok(&self) -> bool {
        self.0 == mqi::MQCC_OK
    }
}
impl std::fmt::Display for ComplCode {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        let code = mqi::cc_to_desc_string(self.0);
        write!(f, "{} - {}", self.0 as i32, code)
    }
}


#[derive(Debug,PartialEq)]
pub struct ReasonCode(i32);
impl ReasonCode {
    pub fn is_ok(&self) -> bool {
        self.0 == mqi::MQRC_NONE
    }
}
impl std::fmt::Display for ReasonCode {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        let code = mqi::rc_to_desc_string(self.0);
        write!(f, "{} - {}", self.0 as i32, code)
    }
}
